/*
 * Copyright (c) 2014, Inria, University Lille 1.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Affero General Public License v3.0
 * which accompanies this distribution, and is available at
 * https://www.gnu.org/licenses/agpl-3.0.html
 *
 * Author : Adel Noureddine
 */

package jalen.sensors.cpu;

import jalen.Agent;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;

public class CPUSensorDVFS implements CPUSensorsInterface {

	/**
	 * Number of cores in CPU, collected from sigar
	 * 从sigar中收集CPU的核数
	 */
	private int numberOfCores;

	/**
	 * Process PID to monitor
	 * 进行监听进程PID
	 */
	private int pid;

	/**
	 * Map of CPU frequencies and the CPU time they spend in each
	 * 对cpu频率和相应的cpu时间进行一一映射
	 */
	private Map<Double, Double> timeInFrequencies;

	/**
	 * Previous CPU total time cpu总处理时间
	 * Previous CPU PID time cpu处理进程PID所使用的时间
	 */
	private Long previousTotalTime, previousPIDTime;

	/**
	 * Check if monitoring has already started or not
	 * 检查是否已经开始监听
	 * Used in calculating cpu percentage for the first time
	 * 用于第一次计算cpu百分比
	 */
	private boolean firstRun;

	/**
	 * Path to the time_in_state file where CPU time data is stored
	 * cpu的时间数据存储在time_in_state文件中
	 * Linux-systems only
	 * 只支持linux操作系统
	 * time_in_state文件在linux系统的路径
	 */
	private String timeInStatePath = "/sys/devices/system/cpu/cpu%?/cpufreq/stats/time_in_state";
	private String curfreqInStatePath ="/sys/devices/system/cpu/cpu%?/cpufreq/scaling_cur_freq";

	/**
	 * Constructor
	 * @param pid Process PID to monitor pid即为进程相应的编号
	 * @param frequenciesMap The map of frequencies and voltage frequenciesMap为cpu频率和电压之间的映射
	 */
	public CPUSensorDVFS(int pid, Map<Double, Double> frequenciesMap) {
		this.pid = pid;//进程的编号
		this.timeInFrequencies = frequenciesMap;//CPU频率和相应CPU时间的映射
		this.numberOfCores = Runtime.getRuntime().availableProcessors();//在cpu运行过程中使用的内核数
		this.previousPIDTime = 0L;//jalen代理前进程pid以运行的CPU时间
		this.previousTotalTime = 0L;//jalen代理前cpu已经运行的总的时间
		this.firstRun = true;//第一次运行
	}

	/**
	 * Get CPU total time
	 * @return CPU total time
	 * 获取cpu运行的总时间
	 */
	private Long getTotalTime() {
		Long result = 0L;

		try (BufferedReader br = new BufferedReader(new FileReader("/proc/stat"))) {
			// Read first line where total cpu time is, and split it
			//读取"/proc/stat"文件的第一行，即总的cpu时间，并以空格分割它，例子如下：
			// Example of line: cpu  586994 19195 123650 2813135 107565 3 2017 0 0 0
			String[] firstLine = br.readLine().split(" ");

			// Sum up all values, except first one which is "cpu" (index 0)
//			将所有的数值相加求和，除了索引为0的第一个字符串"cpu"
			// Index 1 is another space, so start at index 2
//			firstLine[1]是另一个空格，所以从firstLine[2]开始加（cpu和第一个数值之间是两个空格）
//			这里设置了的内核处小于10个
			for (int i=2; i<11; i++) {
				result += Long.valueOf(firstLine[i]);
			}
		} catch (Exception e) {
			Agent.LOGGER.log(Level.WARNING, e.getMessage());
		}

		return result;
	}

	/**
	 * Get CPU PID time
	 * @return CPU PID time
	 * 获取进程pid的cpu处理时间
	 */
	private Long getPIDTime() {
		Long result = 0L;

		try (BufferedReader br = new BufferedReader(new FileReader("/proc/" + this.pid + "/stat"))) {
			// Read first line where cpu time is, and split it
			//进程pid的cpu时间数据保存在文件"/proc/pid/stat中
			// We only need utime and stime (user and system time)
			//我们只需要utime和stime即用户时间和系统时间，同样以空格对第一行数据进行分割，例子如下：
			// Example of line: 25152 (java) S 12564 1685 1685 0 -1 1077960704 155132 412 478 2 11617 1816 0 0 20 0 61 0 2001362 3813126144 99139 18446744073709551615 4194304 4196724 140736365379696 140736365362368 140056419567211 0 0 4096 16796879 18446744073709551615 0 0 17 2 0 0 3 0 0 6294960 6295616 13131776 140736365387745 140736365388341 140736365388341 140736365391821 0
			String[] firstLine = br.readLine().split(" ");

			// Get utime and stime and sum them together
			//获取utime和stime并求和
			// utime is at index 13, stime at index 14
			//其中utime的索引为13，stime的索引为14
			result = Long.valueOf(firstLine[13]) + Long.valueOf(firstLine[14]);
		} catch (Exception e) {
			Agent.LOGGER.log(Level.WARNING, e.getMessage());
		}

		return result;
	}

	@Override
	public double getProcessCPUUsagePercentage() {//依据进程的cpu时间来计算进程的cpu占比
		Long totalTime = this.getTotalTime();
		Long pidTime = this.getPIDTime();

		Long totalDiff = totalTime - this.previousTotalTime;//总的时间差
		Long pidDiff = pidTime - this.previousPIDTime;//pid的时间差

		double result = 0.0;
//        检查是否已经开始监听
		if (!this.firstRun)
			result = ((double) pidDiff) / totalDiff; // / this.numberOfCores;核的个数
		else
			this.firstRun = false;

		this.previousPIDTime = pidTime;//初识的进程pid时间已运行的
		this.previousTotalTime = totalTime;//CPU已运行的总时间

		return result;
	}
//获取各频率下CPU的时间
	@Override
	public Map<Double, Double> getTimeInFrequencies() {
		Map<Double, Double> timeInFrequenciesUpdated = new HashMap<Double, Double>();
		Map<Double, Double> timeInFrequenciesNew = this.getTimeInFrequenciesFromPath();

		for (Map.Entry<Double, Double> entry : this.timeInFrequencies.entrySet()) {
			Double frequency = entry.getKey();
			Double timeFreq = entry.getValue();
			if (timeInFrequenciesNew.get(frequency) != null)
				timeInFrequenciesUpdated.put(frequency, timeFreq - timeInFrequenciesNew.get(frequency));
			else
				timeInFrequenciesUpdated.put(frequency, timeFreq);
		}

		return timeInFrequenciesUpdated;
	}

	/**
	 * Calculate CPU time for each frequency
	 * 计算CPU每个频率下的时间
	 * This function only report the data contained in @timeInStatePath file
	 * 这个方法只是报告timeInStatePath的文件下的数据
	 * This means, this is an aggregated data since start of CPU monitoring by Linux (and not for monitoring cycle)
	 * 这意味着，这是一个整合的数据自CPU被linux监控开始，而不是在jalen监听周期内
	 * @return Map of CPU frequencies and the CPU time spend for each
	 * 返回cpu频率以及相应的该频率下CPU时间的映射关系
	 */
	private Map<Double, Double> getTimeInFrequenciesFromPath() {//从timeInStatePath文件路径中获取cpu各频率下的运行时间
		Map<Double, Double> timeInFrequenciesCache = new HashMap<Double, Double>();

		for (int i = 0; i<this.numberOfCores; i++) {
			String timeInStatePathReal = timeInStatePath.replace("%?", "" + i);

			try (BufferedReader br = new BufferedReader(new FileReader(timeInStatePathReal))) {
				String currentLine = "";
				while ((currentLine = br.readLine()) != null) {
					String[] freqAndtime = currentLine.split(" ");
					Double frequency = Double.valueOf(freqAndtime[0]);
					Double timeFreq = Double.valueOf(freqAndtime[1]);

					if (timeInFrequenciesCache.containsKey(frequency))
						timeInFrequenciesCache.put(frequency, timeInFrequenciesCache.get(frequency) + timeFreq);
					else
						timeInFrequenciesCache.put(frequency, timeFreq);
				}
			} catch (IOException e) {
				Agent.LOGGER.log(Level.WARNING, e.getMessage());
			}
		}

		return timeInFrequenciesCache;
	}
}
